﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.CodeDom;
using AgentSystem.AgentCreator.Util;

namespace AgentSystem.AgentCreator.Sbpel.Activities
{
    /// <summary>
    /// Process Activity. Root node in the sBPEL.
    /// </summary>
    public class ProcessActivity : AbstractActivity
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ProcessActivity"/> class.
        /// </summary>
        /// <param name="processName">Name of the process.</param>
        public ProcessActivity(AbstractActivity parent, string processName) : base(parent)
        {
            Name = processName;
            ParentActivity = null;
            ChildActivities = new List<AbstractActivity>();
        }

        /// <returns>
        /// String representing C# code
        /// </returns>
        public override CodeObject[] GetCodeDom()
        {
            IList<CodeObject> codeObjectToReturn = new List<CodeObject>();

            CodeTypeDeclaration agentClass = new CodeTypeDeclaration();
            agentClass.Name = GetAgentClassName();
            agentClass.BaseTypes.Add(new CodeTypeReference("IAgent"));
            codeObjectToReturn.Add(agentClass);

            //Make Agent Class public and add comment
            agentClass.Attributes = MemberAttributes.Public;
            CodeCommentStatement classComment = new CodeCommentStatement(string.Format("Class for Agent {0}", Name));
            agentClass.Comments.Add(classComment);

            //Create the State Member
            var agentStateType = string.Format("{0}State", agentClass.Name);
            CodeMemberField stateField = new CodeMemberField(agentStateType, Constants.StateMemberName);
            stateField.Attributes = MemberAttributes.Public;
            agentClass.Members.Add(stateField);

            //Create entry point
            CodeMemberMethod mainMethod = CreateRunMethod(agentClass);

            ImplementIAgent(agentClass);
            
            //Get all code from child activities
            foreach (var child in ChildActivities)
            {
                var childCodeDome = child.GetCodeDom();
                bool invokedDirectChild = false;
                foreach (var codeObject in childCodeDome)
                {
                    if (codeObject is CodeMemberField)
                    {
                        agentClass.Members.Add(codeObject as CodeMemberField);
                    }
                    else if (codeObject is CodeMemberMethod)
                    {
                        if (!invokedDirectChild)
                        {
                            invokedDirectChild = true;
                            var invokeMethod = new CodeMethodInvokeExpression(
                                    null,
                                    (codeObject as CodeMemberMethod).Name, // mehtod name
                                    new CodeExpression[] { }); // no parameters

                            mainMethod.Statements.Add(invokeMethod);
                        }

                        agentClass.Members.Add(codeObject as CodeMemberMethod);

                    }
                    else if (codeObject is CodeTypeDeclaration)
                    {
                        codeObjectToReturn.Add(codeObject);
                    }
                    else
                    {
                        mainMethod.Statements.Add(codeObject as CodeExpression);
                    }
                }
            }

            return codeObjectToReturn.ToArray();
        }

        private static void ImplementIAgent(CodeTypeDeclaration agentClass)
        {
            CodeMemberMethod getStateMethod = new CodeMemberMethod();
            getStateMethod.Name = "GetState";
            getStateMethod.Attributes = MemberAttributes.Public;
            getStateMethod.Comments.Add(new CodeCommentStatement("Implementing GetState from IAgent"));
            getStateMethod.ReturnType = new CodeTypeReference("System.Object");
            getStateMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("State")));
            agentClass.Members.Add(getStateMethod);
        }
        /// <summary>
        /// Get the Class for this Agent implementation.
        /// </summary>
        /// <returns>Agent Class Name</returns>
        public string GetAgentClassName()
        {
            return string.Format("Agent{0}", Utils.UppercaseFirst(Utils.RemoveSpecialCharacters(Name)));
        }

        /// <summary>
        /// Creates the Run method.
        /// This method starts the Agent.
        /// </summary>
        /// <param name="agentClass"></param>
        /// <returns></returns>
        private static CodeMemberMethod CreateRunMethod(CodeTypeDeclaration agentClass)
        {
            CodeMemberMethod runMethod = new CodeMemberMethod();
            runMethod.Name = "Run";
            runMethod.Attributes = MemberAttributes.Public;
            runMethod.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference("System.String[]"), "args"));
            agentClass.Members.Add(runMethod);
            return runMethod;
        }
    }
}
